
/**
 * 
 */
myos.Menu = new Class({
	
	Implements: Options,
	
	options: {

		zIndex: 1111,
		
		/*
		 * All css classes associated with menus and options.
		 * 
		 * If an array is given, they will be applied in the order of the array, 
		 * using the last item for all submenus.
		 *
		 * Each class can have the following:
		 * 	(class name)_h = the hover class
		 * 	(class name)_c = the click (or "active") class
		 * 	(class name)_s = the "selected" class (when submenus are open, this class applies to the parent
		 *
		 * TODO - Border classes for fancy corners ;)
  		 * 	(class name)_nw, _n, _ne,... you get the point
 		 * 
		 * "icon" is the class(or array of classes for icons.  The icons will be inserted to the left of the label.
		 * It will only display if the "iconProperty" is set, an image will be injected into this element for each option.
		 * 
		 * "expand" is the class of the expand icon(>).  It will be appended to the right of the option label.
		 * 
		 */
		css: {
			menu: ['menu'],
			menuopt: ['menu_opt'],
			icon: 'menu_icon',
			expand: 'menu_expand'
		},

		/*
		 * Animations -
		 *  none = no animation - use this for top level menus that you want to be static on the page
		 * 		e.g. - ['none','dropin','slide']
		 *  slide = "Slides" the submenu into place.  Slides to
		 *  bloom =  "Expands (blooms)" from the option that creates it
		 * 
		 * ** Modifiers:
		 * (effect):lr = left-to-right
		 * (effect):rl = right-to-left
		 * (effect):ud = top-down
		 * (effect):du = bottom-up
		 * 
		 * Can be given a single option, or an array of options.
		 * If an array is given, they will be applied in the order of the array, 
		 * using the last item for all submenus
		 */
		animations: 'bloom:lr',
		/* Custom Fx.Morph options */
		morphOptions: {transition:Fx.Transitions.Sine.easeOut,onComplete:function(){alert('done with effect');}},
		
		/* the property name of the icon image url(each snapp.Object should have one) */
		iconProperty: null,
		/* the property name that identifies the submenus */
		subMenuProperty: 'options',
		/* the property name that identifies the label */
		labelProperty: 'label',
		
		/* the image path for the expand icon
		 can be a string or an array of strings */
		expandIcon: null,
		
		/*
		 * Events
		 * 
		 * Signature = function(snapp.Object,Event)
		 */
		onClick: null,
		
		/* TODO */
		onMouseEnter: null,
		onMouseLeave: null
	},
	
	/**
	 * Constructor
	 * @param Source Object
	 * @param target element or event
	 * @param options
	 */
	initialize: function(rootObj,target,options) {
		this.rootObject = rootObj;
		// test target type
		this.target = (typeOf(target) == 'event') ? this.genEventElement(event) : $(target); 
		this.setOptions(options);	
	
		// bind events
		//this.doClick.bind(this);
		//this.doMouseEnter.bind(this);
		//this.doMouseLeave.bind(this);
		//this.buildLayer.bind(this);
		
		// if the Source Object is not an array, the next layer must be the top
		//toparr = typeOf(this.rootObject) == 'array' ? this.rootObject : this.rootObject[this.options.subMenuProperty];
		// build the first layer
		this.buildLayer(this.rootObject,this.target,0);
		
	},
	
	/**
	 * Build a list of options
	 * 
	 * @param parent Object
	 * @param parent Element
	 * @param current tier to build
	 */
	buildLayer: function(pObj, pEl, tier) {
		// get menu class
		menuClass = this.getTierOption(this.options.css.menu, tier);
		// initial styles
		stiles = {
			'display': 'none',
			'z-index': this.options.zIndex+tier,
			'position':'absolute'
		}
		// build layer container
		lyr = new Element('div',{'class':menuClass,'styles':stiles}).store('tier',tier);
		// build options
		for	(o=0;o<pObj[this.options.subMenuProperty].length;o++) {
			lyr.grab(this.buildOption(pObj[this.options.subMenuProperty][o],tier));	
		}	
		// do da purtiness..
		this.doFx(pEl,lyr,tier);
		
		pEl.grab(lyr);
		
		return lyr;
	},
	
	/**
	 * Build an option given the Source Object
	 * @param Source Object to build an option from
	 */
	buildOption: function(optob,teer) {
		//alert('getting menu option '+ optob[this.options.labelProperty] + '.\n  Tier = '+tier+'\nOpt Class = '+this.getTierOption(this.options.css.menuopt));
		opt = new Element('div',{
				'class': this.getTierOption(this.options.css.menuopt,teer)
		});
		opt.store('tier',teer);
		opt.store('optObject',optob);
		
		opt.addEvent('click',this.doClick.bind(this));
		
		if (optob[this.options.subMenuProperty]) {
			opt.addEvent('mouseenter',this.doMouseEnter.bind(this));
			opt.addEvent('mouseleave',this.doMouseLeave.bind(this));
		}
		
		opt.set('html',optob[this.options.labelProperty]);
		
		return opt;
	},

	/**
	 * Do the effects for the current tier
	 */
	doFx: function(pOpt,cEl,tier) {
		// get animation string for current tier
		anim = this.getTierOption(this.options.animations,tier);
		animstr = anim.split(':')[0];
		animmod = anim.split(':')[1];
		
		//alert('doing animation: '+anim);
		
		// kick back if none
		if (animstr == 'none') {
			cEl.show();
			return;
		}
		
		// get parent element dimensions
		pdims = pOpt.measure(function(){
			return this.getCoordinates();
		});
		cdims = cEl.getComputedSize();
		
		// morph properties
		mprops = new Object();
		// test the animation to set properties
		switch(animstr){
			// slides
			case 'slide':
				// if up>down or down>up, set height morph property
				if (animmod.contains('u')) mprops.height = [0,cdims.height];
				// if left>right or right>left, set width morph property
				if (animmod.contains('l')) mprops.width = [0,cdims.width];
				
				break;

			// blooms
			case 'bloom':
				// set width and height morph properties
				mprops.width = [0,cdims.width];
				mprops.height = [0,cdims.height];
				
				break;
			
			// appears/fades
			case 'appear':
				// set opacity property
				mprops.opacity = [0,1];
			
				break;
				
			default:
				break;	
			
		}
		// test modifier
		switch(animmod) {
			case 'ud':
				//alert('bottom: '+pdims.bottom+'\nleft: '+pdims.left);
				cEl.setStyles({
					top:pdims.bottom,
					left:pdims.computedLeft
				});
				break;
			case 'du':
				cEl.setStyles({
					bottom: pdims.top,
					left:pdims.computedLeft
				});
				break;
			case 'rl':
				cEl.setStyles({
					right: pdims.left,
					top: pdims.top
				});
				break;
			default:
				cEl.setStyles({
					left: pdims.right,
					top: pdims.top
				});
				break;
		}

		// clone properties for animation reversal
		rprops = Object.clone(mprops);
		// reverse morph property arrays
		for (rp in rprops) {
			rprops[rp] = Array.clone(rprops[rp]).reverse();
		}
		// clone the morph options
		mos = Object.clone(this.options.morphOptions);
		cEl.store('morphOpts',mos);
		
		// add reversal method
		cEl.reverseAnimation = function() {
			mopts = this.retrieve('morphOpts');
			// add onComplete event to trigger element destruction
			mopts.onComplete = function() {
				this.destroy();		
			}.bind(this);
			// start new Morph instance
			rmorph = new Fx.Morph(this,mopts);
			rmorph.start(mprops);		
		}
		
		// show the child element
		cEl.show();
		
		alert('starting animation');
		// do the effect
		eff = new Fx.Morph(cEl,this.options.morphOptions);
		eff.start(mprops);		
	},
	
	/**
	 * 
	 */
	genEventElement: function(event) {
		return new Element('div',
			{
				styles: {
					position:'absolute',
					visibility:'hidden',
					height:'1px',
					width:'1px',
					'top':event.client.y,
					'left':event.client.x
				}
			});
	},
	
	/**
	 * Get the option value for the specified tier. 
	 */
	getTierOption: function(opt,tier) {
		rOpt = opt;
		//alert('checking tier options: '+opt.toString() + ' | tier: '+tier);
		if (typeOf(opt) == 'array') {
			rOpt = opt[tier];
			//alert('Return Opt: '+rOpt);
			if (typeOf(rOpt) == 'null') rOpt = opt.getLast();
		} 
		return rOpt;
	},
	
	/**
	 * 
	 */
	doClick: function(ev) {
		tel = $(ev.target);
		// get option class
		optClass = this.getTierOption(this.options.css.menuopt, tel.retrieve('tier'));
		// add hover class
		tel.addClass(optClass+'_c');
		// call onclick
		this.options.onClick();
	},
	
	doMouseEnter: function(ev) {
		tel = $(ev.target);
		// get option class
		optClass = this.getTierOption(this.options.css.menuopt, tel.retrieve('tier'));
		// add hover class
		tel.addClass(optClass+'_h');
		// build and store submenu
		tel.store('submenu',this.buildLayer(tel.retrieve('optObject'),tel,tel.retrieve('tier')+1));
	},
	
	doMouseLeave: function(ev) {
		tel = $(ev.target);
		// get option class
		optClass = this.getTierOption(this.options.css.menuopt, tel.retrieve('tier'));
		// remove the hover class
		tel.removeClass(optClass+'_h');
		// destroy the hover class
		while(typeOf(tel.retrieve('submenu')) == 'null') {
			tel = tel.getParent(); 		
		}
		tel.retrieve('submenu').reverseAnimation();
	},
	
	clearSelection: function(tier) {
		optClass = this.getTierOption(this.options.css.menuopt, tier);

		opts = $$(this.options.css.menuopt);
		opts.each(function(opt){
			opt.removeClass(optClass+'.S');
		});
	}
});

